/*

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     licenses@blazegraph.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/
/*
 * Created on Jun 19, 2008
 */

package com.bigdata.bop.bindingSet;

import java.util.Iterator;
import java.util.Map;

import junit.framework.TestCase2;

import com.bigdata.bop.Constant;
import com.bigdata.bop.IBindingSet;
import com.bigdata.bop.IConstant;
import com.bigdata.bop.IVariable;
import com.bigdata.bop.Var;
import com.bigdata.io.SerializerUtil;

/**
 * Unit tests for {@link IBindingSet}.
 * <p>
 * Note:
 * <ul>
 * <li>a) these tests assume that the values held for a given key are not
 * cloned, i.e. comparison is done by '==' and not '.equals' (this is true
 * except for the Serializatoin tests, where the {@link Var} references will be
 * preserved but the {@link IConstant}s will be distinct).</li>
 * <li>b) keys with the same 'name' are a unique object.</li>
 * </ul>
 * 
 * @author <a href="mailto:dmacgbr@users.sourceforge.net">David MacMillan</a>
 * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
 * @version $Id$
 */
public abstract class TestIBindingSet extends TestCase2 {

    /**
     * 
     */
    public TestIBindingSet () {}

    /**
     * @param name
     */
    public TestIBindingSet ( String name ) { super ( name ) ; }

    /**
     * Unit test for {@link IBindingSet#isBound(IVariable)}
     */
    public void testIsBound ()
    {
        Var<?> a = Var.var ( "a" ) ;
        Var<?> b = Var.var ( "b" ) ;
        Var<?> c = Var.var ( "a" ) ;

        IBindingSet bs = newBindingSet ( new IVariable [] { a }, new IConstant [] { new Constant<Integer> ( 1 ) } ) ;

        assertTrue ( "bound expected, same variable", bs.isBound ( a ) ) ;
        assertFalse ( "not bound expected", bs.isBound ( b ) ) ;
        assertTrue ( "bound expected, equivalent variable", bs.isBound ( c ) ) ;
    }

    /**
     * Unit test for {@link IBindingSet#set(IVariable,IConstant)}
     */
    public void testSet ()
    {
        Var<?> var = Var.var ( "a" ) ;
        Constant<Integer> val1 = new Constant<Integer> ( 1 ) ;
        Constant<Integer> val2 = new Constant<Integer> ( 2 ) ;

        IBindingSet bs = newBindingSet ( 2 ) ;

        try { bs.set ( null,  val1 ) ; fail ( "IllegalArgumentException expected, var was null" ) ; }
        catch ( IllegalArgumentException e ) {}

        try { bs.set ( var,  null ) ; fail ( "IllegalArgumentException expected, val was null" ) ; }
        catch ( IllegalArgumentException e ) {}

        bs.set ( var, val1 ) ;
        assertTrue ( val1 == bs.get ( var ) ) ;

        bs.set ( var, val2 ) ;
        assertTrue ( val2 == bs.get ( var ) ) ;
    }

    /**
     * Unit test for {@link IBindingSet#get(IVariable)}
     */
    public void testGet ()
    {
        Var<?> var1 = Var.var ( "a" ) ;
        Var<?> var2 = Var.var ( "b" ) ;
        Constant<Integer> val1 = new Constant<Integer> ( 1 ) ;

        IBindingSet bs = newBindingSet ( new IVariable [] { var1 }, new IConstant [] { val1 } ) ;

        try { bs.get ( null ) ; fail ( "IllegalArgumentException expected, var was null" ) ; }
        catch ( IllegalArgumentException e ) {}

        assertTrue ( val1 == bs.get ( var1 ) ) ;
        assertTrue ( null == bs.get ( var2 ) ) ;
    }

    /**
     * Unit test for {@link IBindingSet#clear(IVariable)}
     */
    public void testClear ()
    {
        Var<?> var1 = Var.var ( "a" ) ;
        Var<?> var2 = Var.var ( "b" ) ;
        Constant<Integer> val1 = new Constant<Integer> ( 1 ) ;
        Constant<Integer> val2 = new Constant<Integer> ( 2 ) ;

        IBindingSet bs = newBindingSet ( new IVariable [] { var1, var2 }, new IConstant [] { val1, val2 } ) ;

        try { bs.clear ( null ) ; fail ( "IllegalArgumentException expected, var was null" ) ; }
        catch ( IllegalArgumentException e ) {}

        bs.clear ( var1 ) ;
        assertTrue ( null == bs.get ( var1 ) ) ;
        assertTrue ( val2 == bs.get ( var2 ) ) ;

        bs.clear ( var2 ) ;
        assertTrue ( null == bs.get ( var2 ) ) ;
        assertTrue ( 0 == bs.size () ) ;
    }

    /**
     * Unit test for {@link IBindingSet#clearAll()}
     */
    public void testClearAll ()
    {
        Var<?> var1 = Var.var ( "a" ) ;
        Var<?> var2 = Var.var ( "b" ) ;
        Constant<Integer> val1 = new Constant<Integer> ( 1 ) ;
        Constant<Integer> val2 = new Constant<Integer> ( 2 ) ;

        IBindingSet bs = newBindingSet ( new IVariable [] { var1, var2 }, new IConstant [] { val1, val2 } ) ;

        bs.clearAll () ;
        assertTrue ( null == bs.get ( var1 ) ) ;
        assertTrue ( null == bs.get ( var2 ) ) ;
        assertTrue ( 0 == bs.size () ) ;
    }

    /**
     * Unit test for {@link IBindingSet#size()}
     */
    public void testSize ()
    {
        Var<?> var1 = Var.var ( "a" ) ;
        Var<?> var2 = Var.var ( "b" ) ;
        Constant<Integer> val1 = new Constant<Integer> ( 1 ) ;
        Constant<Integer> val2 = new Constant<Integer> ( 2 ) ;

        IBindingSet bs = newBindingSet ( 2 ) ;

        assertTrue ( 0 == bs.size () ) ;

        bs.set ( var1, val1 ) ;
        bs.set ( var2, val2 ) ;
        assertTrue ( 2 == bs.size () ) ;

        bs.clear ( var2 ) ;
        assertTrue ( 1 == bs.size () ) ;
    }

    /**
     * Unit test for {@link IBindingSet#iterator()}
     */
    public void testIterator ()
    {
        final Var<?> var1 = Var.var("a");
        final Var<?> var2 = Var.var("b");
        final Constant<Integer> val1 = new Constant<Integer>(1);
        final Constant<Integer> val2 = new Constant<Integer>(2);

        final IBindingSet bs = newBindingSet(//
                new IVariable[] { var1, var2 },//
                new IConstant[] { val1, val2 }//
                );

        int nvisited = 0 ;
        for (Iterator<Map.Entry<IVariable, IConstant>> i = bs.iterator(); i
                .hasNext();) {

            final Map.Entry<IVariable, IConstant> e = i.next();
            final IVariable<?> var = e.getKey();

            if      ( var1 == var ) assertTrue ( "wrong value", val1 == e.getValue () ) ;
            else if ( var2 == var ) assertTrue ( "wrong value", val2 == e.getValue () ) ;
            else fail ( "unexpected variable: " + var ) ;

//            try {
//                i.remove();
//                fail("UnsupportedOperationException expected, iterator remove");
//            } catch (UnsupportedOperationException ex) {
//            }
            i.remove();
            nvisited++;
        }
        assertEquals("size", 2, nvisited);

        // Should be empty afterwards.
        assertEquals(newBindingSet(//
                new IVariable[] {},//
                new IConstant[] {}//
                ), bs);

    }

    /**
     * Unit test for {@link IBindingSet#vars()}
     */
    public void testVars ()
    {
        Var<?> var1 = Var.var ( "a" ) ;
        Var<?> var2 = Var.var ( "b" ) ;
        Constant<Integer> val1 = new Constant<Integer> ( 1 ) ;
        Constant<Integer> val2 = new Constant<Integer> ( 2 ) ;

        IBindingSet bs = newBindingSet ( new IVariable [] { var1, var2 }, new IConstant [] { val1, val2 } ) ;

        int n = 0 ;
        for ( Iterator<IVariable> i = bs.vars (); i.hasNext (); )
        {
            IVariable<?> var = i.next () ;

            if ( var1 != var && var2 != var )
                fail ( "unexpected variable: " + var ) ;

            try { i.remove () ; fail ( "UnsupportedOperationException expected, iterator remove" ) ; }
            catch ( UnsupportedOperationException e ) {}
            n++ ;
        }
        assertTrue ( "wrong count", 2 == n ) ;
    }

    /**
     * Unit test for {@link IBindingSet#copy(IVariable[])}
     */
    public void testCopy ()
    {
        Var<?> var1 = Var.var ( "a" ) ;
        Var<?> var2 = Var.var ( "b" ) ;
        Var<?> var3 = Var.var ( "c" ) ;
        Var<?> var4 = Var.var ( "d" ) ;
        Var<?> var5 = Var.var ( "e" ) ;
        Constant<Integer> val1 = new Constant<Integer> ( 1 ) ;
        Constant<Integer> val2 = new Constant<Integer> ( 2 ) ;
        Constant<Integer> val3 = new Constant<Integer> ( 3 ) ;
        Constant<Integer> val4 = new Constant<Integer> ( 4 ) ;
        Constant<Integer> val5 = new Constant<Integer> ( 5 ) ;
        
        IBindingSet bs = newBindingSet ( new IVariable [] { var1, var2, var3, var4, var5 }
                                       , new IConstant [] { val1, val2, val3, val4, val5 }
                                       ) ;
        
		assertEqual(
				bs.copy(null/* variablesToKeep */), //
				new IVariable[] { var1, var2, var3, var4, var5 },
				new IConstant[] { val1, val2, val3, val4, val5 }//
		);

        IBindingSet bs2 = bs.copy ( new IVariable [] { var1, var3, var5 } ) ;

        assertTrue ( 3 == bs2.size () ) ;
        for ( IVariable<?> v : new IVariable [] { var1, var3, var5 } )
            assertTrue ( bs2.get ( v ).equals ( bs.get ( v ) ) ) ;
    }

    /**
     * Unit test for {@link IBindingSet#equals(Object)}
     */
    public void testEquals ()
    {
        Var<?> var1 = Var.var ( "a" ) ;
        Var<?> var2 = Var.var ( "b" ) ;
        Var<?> var3 = Var.var ( "c" ) ;
        Constant<Integer> val1 = new Constant<Integer> ( 1 ) ;
        Constant<Integer> val2 = new Constant<Integer> ( 2 ) ;
        Constant<Integer> val3 = new Constant<Integer> ( 3 ) ;
        
        IBindingSet bs1 = newBindingSet ( new IVariable [] { var1, var2 }, new IConstant [] { val1, val2 } ) ;
        IBindingSet bs2 = newBindingSet ( new IVariable [] { var1, var2 }, new IConstant [] { val1, val2 } ) ;
        IBindingSet bs3 = newBindingSet ( new IVariable [] { var2, var1 }, new IConstant [] { val2, val1 } ) ;
        IBindingSet bs4 = newBindingSet ( new IVariable [] { var1, var2 }, new IConstant [] { val1, val3 } ) ;
        IBindingSet bs5 = newBindingSet ( new IVariable [] { var1, var3 }, new IConstant [] { val1, val3 } ) ;
        IBindingSet bs6 = newBindingSet ( new IVariable [] { var1, var2, var3 }, new IConstant [] { val1, val2, val3 } ) ;
        IBindingSet bs7 = newBindingSet ( new IVariable [] { var1 }, new IConstant [] { val1 } ) ;

        assertTrue ( "expected equal: same bindings, same order", bs1.equals ( bs2 ) ) ;
        assertTrue ( "expected equal: same bindings, different order", bs1.equals ( bs3 ) ) ;
        assertTrue ( "expected not equal: different value", !bs1.equals ( bs4 ) ) ;
        assertTrue ( "expected not equal: different variable", !bs1.equals ( bs5 ) ) ;
        assertTrue ( "expected not equal: subsetOf ( this, that )", !bs1.equals ( bs6 ) ) ;
        assertTrue ( "expected not equal: subsetOf ( that, this )", !bs1.equals ( bs7 ) ) ;
    }

    /**
     * Unit test for {@link IBindingSet#hashCode()}
     */
    public void testHashCode ()
    {
        Var<?> var1 = Var.var ( "a" ) ;
        Var<?> var2 = Var.var ( "b" ) ;
        Constant<Integer> val1 = new Constant<Integer> ( 1 ) ;
        Constant<Integer> val2 = new Constant<Integer> ( 2 ) ;
        
        IBindingSet bs1 = newBindingSet ( new IVariable [] { var1, var2 }, new IConstant [] { val1, val2 } ) ;
        IBindingSet bs2 = newBindingSet ( new IVariable [] { var1, var2 }, new IConstant [] { val1, val2 } ) ;
        IBindingSet bs3 = newBindingSet ( new IVariable [] { var2, var1 }, new IConstant [] { val2, val1 } ) ;
        IBindingSet bs4 = newBindingSet ( new IVariable [] { var2 }, new IConstant [] { val2 } ) ;

        assertTrue ( "expected equal: same bindings, same order", bs1.hashCode () == bs2.hashCode () ) ;
        assertTrue ( "expected equal: same bindings, different order", bs1.hashCode () == bs3.hashCode () ) ;

        //
        // After mutation. Not sure that this really proves anything, although in most cases I guess that
        // the original value of bs1.hasCode () will not equal the subsequent value or that of bs4.hashCode ()
        //
        bs1.clear ( var1 ) ;
        assertTrue ( "expected equal: same bindings after mutation", bs1.hashCode () == bs4.hashCode () ) ;
    }

//    /*
//     * push()/pop() tests.
//     * 
//     * In addition to testing push() and pop(), we have to test that copy() and
//     * clone() operate correctly in the presence of nested symbol tables, and
//     * that the visitation patterns for the bindings operate correctly when
//     * there are nested symbol tables. For example, if there "y" is bound at
//     * level zero, a push() is executed, and then "x" is bound at level one. The
//     * visitation pattern must visit both "x" and "y".
//     */
//
//    public void test_pushPop1() {
//
//        final Var<?> var1 = Var.var("var1"); // visible and bound in query and subquery.
//        final Var<?> var2 = Var.var("var2"); // visible only in subquery and not projected.
//        final Var<?> var3 = Var.var("var3"); // visible in query and subquery; bound in subquery.
//        final Var<?> var4 = Var.var("var4"); // visible and bound only in query
//
//        final Constant<Integer> val1 = new Constant<Integer>(1);
//        final Constant<Integer> val2 = new Constant<Integer>(2);
//        final Constant<Integer> val3 = new Constant<Integer>(3);
//        final Constant<Integer> val4 = new Constant<Integer>(4);
//        final Constant<Integer> val4a = new Constant<Integer>(40);
//
//        // the "projected" variables.
//        final IVariable<?>[] vars = new IVariable[] { var1, var3 };
//
//        final IBindingSet bs1 = newBindingSet(4/* size */);
//
//        /*
//         * bind var1 in the base symbol table.
//         */
//        bs1.set(var1, val1);
//        bs1.set(var4, val4);
//        assertEqual(bs1, new IVariable[] { var1, var4 }, new IConstant[] { val1, val4 });
//
//        /*
//         * push a symbol table onto the stack
//         */
//        bs1.push(vars);
//
//        // verify initial symbol table for subquery.
//        assertEqual(bs1, new IVariable[] { var1 }, new IConstant[] { val1 });
//        
//        // bind an unbound variables.
//        bs1.set(var2, val2);
//        bs1.set(var3, val3);
//        bs1.set(var4, val4a); // note: variable and binding are distinct in this scope.
//        assertEqual(bs1, new IVariable[] { var1, var2, var3, var4 }, new IConstant[] {
//                val1, val2, val3, val4a });
//
//        // verify clone shows the same symbol table.
//        final IBindingSet copy = bs1.clone();
//        assertEqual(copy, new IVariable[] { var1, var2, var3, var4 }, new IConstant[] {
//                val1, val2, val3, val4a });
//        assertEquals(bs1, copy);
//        
//        // pop the stack.
//        bs1.pop(vars);
//
//        // verify projected variables were copied and local vars discarded.
//        assertEqual(bs1, new IVariable[] { var1, var3, var4 }, new IConstant[] {
//                val1, val3, val4 });
//
//        // verify copy was not effected.
//        assertEqual(copy, new IVariable[] { var1, var2, var3, var4 }, new IConstant[] {
//                val1, val2, val3, val4a });
//
//        copy.pop(vars);
//
//        // verify projected variables were copied and local vars discarded.
//        assertEqual(bs1, new IVariable[] { var1, var3, var4 }, new IConstant[] {
//                val1, val3, val4 });
//        
//        // copy and original should now be equal again.
//        assertEquals(bs1, copy);
//
//        /*
//         * Verify that we can not pop() off the last symbol table in the stack.
//         */
//        try {
//            bs1.pop(vars);
//            fail("Expecting: " + IllegalStateException.class);
//        } catch (IllegalStateException ex) {
//            if (log.isInfoEnabled())
//                log.info("Ignoring expected exception: " + ex);
//        }
//
//    }

    public void test_serialization() {
    	
        final Var<?> var1 = Var.var ( "a" ) ;
        final Var<?> var2 = Var.var ( "b" ) ;
        final Constant<Integer> val1 = new Constant<Integer> ( 1 ) ;
        final Constant<Integer> val2 = new Constant<Integer> ( 2 ) ;

		final IBindingSet bs1 = newBindingSet(2/* size */);

		bs1.set(var1, val1);

		bs1.set(var2, val2);

		assertEqual(bs1, new IVariable[] { var1, var2 }, new IConstant[] {
				val1, val2 });

		final IBindingSet bs2 = (IBindingSet) SerializerUtil
				.deserialize(SerializerUtil.serialize(bs1));

		assertEquals(bs1, bs2);

    }
    
    /*
     * Hooks for testing specific implementations.
     */

    protected abstract IBindingSet newBindingSet ( IVariable<?> vars [], IConstant<?> vals [] ) ;
    protected abstract IBindingSet newBindingSet ( int size ) ;

	/**
	 * Compare actual and expected, where the latter is expressed using
	 * (vars,vals).
	 * <p>
	 * Note: This does not follow the junit pattern for asserts, which puts the
	 * expected data first.
	 * 
	 * @param actual
	 * @param vars
	 * @param vals
	 */
    protected void assertEqual(final IBindingSet actual,
            final IVariable<?> vars[], final IConstant<?> vals[]) {

        assertEquals("size", actual.size(), vars.length);
        
        for (int i = 0; i < vars.length; i++) {

            if (vals[i] != actual.get(vars[i])) {

                assertEquals(vars[i].toString(), vals[i], actual.get(vars[i]));
                
            }

        }
        
    }

	protected void assertEquals(IBindingSet expected, IBindingSet actual) {

		// expected variables in some order.
		final Iterator<IVariable> evars = expected.vars();
		
		// actual variables in some order (the order MAY be different).
		final Iterator<IVariable> avars = actual.vars();
		
		while(evars.hasNext()) {

			// Some variable for which we expect a binding.
			final IVariable evar = evars.next();
			
			if(!avars.hasNext()) {
			
				fail("actual does not bind enough variables.");
				
			}

			// consume and ignore actual variable since the order may differ 
			avars.next();

			// The expected binding for some variable.
			final IConstant eval = expected.get(evar);

			// The actual binding for the same variable.
			final IConstant aval = actual.get(evar);

			// Verify that the bound values compare as equals.
			assertEquals(evar.getName(), eval, aval);
			
		}
		
		if(avars.hasNext()) {
			
			fail("actual binds more variables than expected.");
			
		}
		
	}

}
